package org.cocos2d.opengl;

import org.cocos2d.nodes.CCDirector;

import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.microedition.khronos.opengles.GL10;

/**
 * This class performs tasks on the side of OpenGL thread.
 * CCTexture2D calls perform() in finalize method, and texId is queued to be
 * deleted later.
 *
 * @author genius
 */

public class GLResourceHelper {

    private static GLResourceHelper _sharedResourceHelper = new GLResourceHelper();
    private ConcurrentLinkedQueue<GLResorceTask> taskQueue;
    private Map<Resource, GLResourceLoader> reloadMap;
    private volatile long glThreadID;
    private volatile boolean reloadTaskIsInQueue;
    private volatile boolean inUpdate = false;
    public GLResourceHelper() {
        taskQueue = new ConcurrentLinkedQueue<GLResorceTask>();
        reloadMap = Collections.synchronizedMap(new WeakHashMap<GLResourceHelper.Resource, GLResourceHelper.GLResourceLoader>());
    }

    /**
     * singleton of the CCTouchDispatcher
     */
    public static GLResourceHelper sharedHelper() {
        return _sharedResourceHelper;
    }

    public void setGlThreadID(long glThreadID) {
        this.glThreadID = glThreadID;
    }

    public void addLoader(final Resource res, final GLResourceLoader loader, boolean addTask) {
        if (addTask) {
            GLResorceTask task = new GLResorceTask() {
                @Override
                public void perform(GL10 gl) {
                    loader.load(res);
                    reloadMap.put(res, loader);
                }
            };
            perform(task);
        } else {
            reloadMap.put(res, loader);
        }
    }

    /**
     * This should be called only when recreating GL context
     */
    public void reloadResources() {
        if (reloadTaskIsInQueue)
            return;

        reloadTaskIsInQueue = true;
        taskQueue.add(new GLResorceTask() {
            @Override
            public void perform(GL10 gl) {
                synchronized (reloadMap) {
                    for (Entry<Resource, GLResourceLoader> entry : reloadMap.entrySet()) {
                        Resource res = entry.getKey();
                        if (res != null)
                            entry.getValue().load(res);
                    }
                }
                reloadTaskIsInQueue = false;
            }
        });
    }

    /**
     * Add OGL task in queue. If perform is called from another task
     * then task is performed immediately.
     *
     * @param res GL task
     */
    public void perform(GLResorceTask res) {
        if (inUpdate && Thread.currentThread().getId() == glThreadID) {
            res.perform(CCDirector.gl);
        } else {
            taskQueue.add(res);
        }
    }

    /**
     * Method is called from update cycle,
     * perform all tasks in GL thread
     *
     * @param gl
     */
    public void update(GL10 gl) {
        if (taskQueue.size() > 0) {

            GLResorceTask res;
            while ((res = taskQueue.poll()) != null) {
                res.perform(gl);
            }
        }
    }

    public void setInUpdate(boolean inUpd) {
        inUpdate = inUpd;
    }

    public interface GLResorceTask {
        void perform(GL10 gl);
    }

    /**
     * GL resources should implement this
     */
    public interface Resource {
    }

    /**
     * These objects are stored in reloadQueue,
     * they should be removed manually,
     * DO NOT STORE REFERENCE TO CCTexture2D in TextureLoader,
     * GLResourceLoader is called in finalize()
     */
    public interface GLResourceLoader {
        void load(Resource res);
    }
}
